#include "Interpreter.h"

namespace FlooP
{
	Interpreter::Interpreter (Environment& env)
	:
		m_env (env)
	{}

	void Interpreter::execute ()
	{
		std::shared_ptr<Procedure> func = m_env.find_procedure ("MAIN");

		if (func.get () == nullptr) {
			throw Error ("Interpreter : PROCEDURE MAIN [] does not exist.");
		}

		m_frames.push (CallFrame (func));
		m_scopes.push (func->scope ());

		while (m_frames.size () > 0) {
			CallFrame& frame = m_frames.top ();
			Instruction& instruction = (*frame.m_func->code ())[frame.m_address];

			frame.m_address++;

			switch (instruction.m_op)
			{
				case OP_NOP: break;
				case OP_PUSH: m_stack.push (instruction.m_object); break;
				case OP_POP:  m_stack.pop (); break;
				case OP_COPY: m_stack.push (m_stack.top ()); break;

				case OP_CALL:
				{
					std::shared_ptr<Procedure> call_func = m_env.find_procedure (dynamic_cast<String*> (instruction.m_object.get ())->text ());

					if (call_func.get () == nullptr) {
						throw Error ("Interpreter : function '%s' does not exist.", dynamic_cast<String*> (instruction.m_object.get ())->text ());
					}

					m_frames.push (CallFrame (call_func));
					call_func->scope ()->reset ();
					m_scopes.push (std::shared_ptr<Scope> (new Scope (*call_func->scope ())));
				}
				break;

				case OP_RETURN:
				{
					// Before we return, push the value of the output onto the stack
					m_stack.push (m_scopes.top ()->output ());
					m_frames.pop ();
					m_scopes.pop ();
				}
				break;

				case OP_ROUTPUT:
				{
					m_stack.push (m_scopes.top ()->output ());
				}
				break;

				case OP_RCELL:
				{
					m_stack.push (m_scopes.top ()->cell (instruction.m_arg));
				}
				break;

				case OP_SOUTPUT:
				{
					m_scopes.top ()->set_output (m_stack.top ());
				}
				break;

				case OP_SCELL:
				{
					m_scopes.top ()->set_cell (m_stack.top (), instruction.m_arg);
				}
				break;

				case OP_SET:
				{
					const std::string& name = dynamic_cast<String*> (instruction.m_object.get ())->text ();
					m_scopes.top ()->set (name, m_stack.top ());
				}
				break;

				case OP_REF:
				{
					const std::string& name = dynamic_cast<String*> (instruction.m_object.get ())->text ();
					m_stack.push (m_scopes.top ()->find (name));
				}
				break;

				case OP_ABORT:
				case OP_QUIT:
				case OP_BR:
				{
					frame.m_address = instruction.m_arg;
				}
				break;

				case OP_BRT:
				{
					if (m_stack.top ()->type () == Object::NUMBER) {
						if (reinterpret_cast<Number*> (m_stack.top ().get ())->val () == 1) {
							frame.m_address = instruction.m_arg;
						}
					}
					m_stack.pop ();
				}
				break;

				case OP_BRF:
				{
					if (m_stack.top ()->type () == Object::NUMBER) {
						if (reinterpret_cast<Number*> (m_stack.top ().get ())->val () == 0) {
							frame.m_address = instruction.m_arg;
						}
					}
					m_stack.pop ();
				}
				break;

				case OP_ADD:
				{
					std::shared_ptr<Object> right = m_stack.top ();
					m_stack.pop ();

					std::shared_ptr<Object> left = m_stack.top ();;
					m_stack.pop ();
					
					switch (left->type ())
					{
						case Object::NUMBER:
						{
							switch (right->type ())
							{
								case Object::NUMBER:
								{
									m_stack.push (std::shared_ptr<Object> (new Number (dynamic_cast<Number*> (left.get ())->val () + dynamic_cast<Number*> (right.get ())->val ())));
								}
								break;

								default:
								{
									throw Error ("Interpreter : Type mismatch on operator '+'.");
								}
								break;
							}
						}
						break;

						default:
						{
							throw Error ("Interpreter : Invalid arguments to operator '+'.");
						}
						break;
					}
				}
				break;

				case OP_MUL:
				{
					std::shared_ptr<Object> right = m_stack.top ();
					m_stack.pop ();

					std::shared_ptr<Object> left = m_stack.top ();;
					m_stack.pop ();
					
					switch (left->type ())
					{
						case Object::NUMBER:
						{
							switch (right->type ())
							{
								case Object::NUMBER:
								{
									m_stack.push (std::shared_ptr<Object> (new Number (dynamic_cast<Number*> (left.get ())->val () * dynamic_cast<Number*> (right.get ())->val ())));
								}
								break;

								default:
								{
									throw Error ("Interpreter : Type mismatch on operator '*'.");
								}
								break;
							}
						}
						break;

						default:
						{
							throw Error ("Interpreter : Invalid arguments to operator '*'.");
						}
						break;
					}
				}
				break;

				case OP_OR:
				{
					std::shared_ptr<Object> right = m_stack.top ();
					m_stack.pop ();

					std::shared_ptr<Object> left = m_stack.top ();;
					m_stack.pop ();
					
					switch (left->type ())
					{
						case Object::NUMBER:
						{
							switch (right->type ())
							{
								case Object::NUMBER:
								{
									if (dynamic_cast<Number*> (left.get ())->val () || dynamic_cast<Number*> (right.get ())->val ()) {
										m_stack.push (std::shared_ptr<Object> (new Number (1)));
									} else {
										m_stack.push (std::shared_ptr<Object> (new Number (0)));
									}
								}
								break;


								default:
								{
									throw Error ("Interpreter : Type mismatch on operator 'OR'.");
								}
								break;
							}
						}
						break;

						default:
						{
							throw Error ("Interpreter : Invalid arguments to operator 'OR'.");
						}
						break;
					}
				}
				break;

				case OP_AND:
				{
					std::shared_ptr<Object> right = m_stack.top ();
					m_stack.pop ();

					std::shared_ptr<Object> left = m_stack.top ();;
					m_stack.pop ();
					
					switch (left->type ())
					{
						case Object::NUMBER:
						{
							switch (right->type ())
							{
								case Object::NUMBER:
								{
									if (dynamic_cast<Number*> (left.get ())->val () && dynamic_cast<Number*> (right.get ())->val ()) {
										m_stack.push (std::shared_ptr<Object> (new Number (1)));
									} else {
										m_stack.push (std::shared_ptr<Object> (new Number (0)));
									}
								}
								break;


								default:
								{
									throw Error ("Interpreter : Type mismatch on operator 'AND'.");
								}
								break;
							}
						}
						break;

						default:
						{
							throw Error ("Interpreter : Invalid arguments to operator 'AND'.");
						}
						break;
					}
				}
				break;

				case OP_EQ:
				{
					std::shared_ptr<Object> right = m_stack.top ();
					m_stack.pop ();

					std::shared_ptr<Object> left = m_stack.top ();;
					m_stack.pop ();

					switch (left->type ())
					{
						case Object::NUMBER:
						{
							switch (right->type ())
							{
								case Object::NUMBER:
								{
									if (dynamic_cast<Number*> (left.get ())->val () == dynamic_cast<Number*> (right.get ())->val ()) {
										m_stack.push (std::shared_ptr<Object> (new Number (1)));
									} else {
										m_stack.push (std::shared_ptr<Object> (new Number (0)));
									}
								}
								break;

								default:
								{
									m_stack.push (std::shared_ptr<Object> (new Number (1)));
								}
								break;
							}
						}
						break;
					}
				}
				break;

				case OP_LT:
				{
					std::shared_ptr<Object> right = m_stack.top ();
					m_stack.pop ();

					std::shared_ptr<Object> left = m_stack.top ();;
					m_stack.pop ();
					
					switch (left->type ())
					{
						case Object::NUMBER:
						{
							switch (right->type ())
							{
								case Object::NUMBER:
								{
									if (dynamic_cast<Number*> (left.get ())->val () < dynamic_cast<Number*> (right.get ())->val ()) {
										m_stack.push (std::shared_ptr<Object> (new Number (1)));
									} else {
										m_stack.push (std::shared_ptr<Object> (new Number (0)));
									}
								}
								break;

								default:
								{
									throw Error ("Interpreter : Type mismatch on operator '<'.");
								}
								break;
							}
						}
						break;

						default:
						{
							throw Error ("Interpreter : Invalid arguments to operator '<'.");
						}
						break;
					}
				}
				break;

				case OP_GT:
				{
					std::shared_ptr<Object> right = m_stack.top ();
					m_stack.pop ();

					std::shared_ptr<Object> left = m_stack.top ();;
					m_stack.pop ();
					
					switch (left->type ())
					{
						case Object::NUMBER:
						{
							switch (right->type ())
							{
								case Object::NUMBER:
								{
									if (dynamic_cast<Number*> (left.get ())->val () > dynamic_cast<Number*> (right.get ())->val ()) {
										m_stack.push (std::shared_ptr<Object> (new Number (1)));
									} else {
										m_stack.push (std::shared_ptr<Object> (new Number (0)));
									}
								}
								break;

								default:
								{
									throw Error ("Interpreter : Type mismatch on operator '>'.");
								}
								break;
							}
						}
						break;

						default:
						{
							throw Error ("Interpreter : Invalid arguments to operator '>'.");
						}
						break;
					}
				}
				break;
			}
		}

		std::cout << reinterpret_cast<Number*> (m_stack.top ().get ())->val ();
	}
}